
package com.j256.ormlite.stmt.mapped;

import java.sql.SQLException;

import com.j256.ormlite.field.FieldType;
import com.j256.ormlite.field.SqlType;
import com.j256.ormlite.logger.Log.Level;
import com.j256.ormlite.stmt.ArgumentHolder;
import com.j256.ormlite.stmt.PreparedDelete;
import com.j256.ormlite.stmt.PreparedQuery;
import com.j256.ormlite.stmt.PreparedUpdate;
import com.j256.ormlite.stmt.StatementBuilder;
import com.j256.ormlite.stmt.StatementBuilder.StatementType;
import com.j256.ormlite.support.CompiledStatement;
import com.j256.ormlite.support.DatabaseConnection;
import com.j256.ormlite.table.TableInfo;

/**
 * Mapped statement used by the {@link StatementBuilder#prepareStatement(Long)}
 * method.
 * 
 * @author graywatson
 */
public class MappedPreparedStmt<T, ID> extends BaseMappedQuery<T, ID> implements PreparedQuery<T>,
        PreparedDelete<T>, PreparedUpdate<T> {

    private final ArgumentHolder[] argHolders;
    private final Long limit;
    private final StatementType type;

    public MappedPreparedStmt(TableInfo<T, ID> tableInfo, String statement,
            FieldType[] argFieldTypes, FieldType[] resultFieldTypes, ArgumentHolder[] argHolders,
            Long limit, StatementType type) {
        super(tableInfo, statement, argFieldTypes, resultFieldTypes);
        this.argHolders = argHolders;
        // this is an Integer because it may be null
        this.limit = limit;
        this.type = type;
    }

    @Override
    public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type)
            throws SQLException {
        return compile(databaseConnection, type, DatabaseConnection.DEFAULT_RESULT_FLAGS);
    }

    @Override
    public CompiledStatement compile(DatabaseConnection databaseConnection, StatementType type,
            int resultFlags) throws SQLException {
        if (this.type != type) {
            throw new SQLException("Could not compile this " + this.type
                    + " statement since the caller is expecting a " + type
                    + " statement.  Check your QueryBuilder methods.");
        }
        CompiledStatement stmt = databaseConnection.compileStatement(statement, type,
                argFieldTypes, resultFlags);
        return compileStatement(databaseConnection, stmt);
    }

    @Override
    public String getStatement() {
        return statement;
    }

    @Override
    public StatementType getType() {
        return type;
    }

    @Override
    public void setArgumentHolderValue(int index, Object value) throws SQLException {
        if (index < 0) {
            throw new SQLException("argument holder index " + index + " must be >= 0");
        }
        if (argHolders.length <= index) {
            throw new SQLException("argument holder index " + index + " not valid, only "
                    + argHolders.length + " in statement");
        }
        argHolders[index].setValue(value);
    }

    private CompiledStatement compileStatement(DatabaseConnection databaseConnection,
            CompiledStatement stmt) throws SQLException {
        boolean ok = false;
        try {
            if (limit != null) {
                // we use this if SQL statement LIMITs are not supported by this
                // database type
                stmt.setMaxRows(limit.intValue());
            }
            // set any arguments if we are logging our object
            Object[] argValues = null;
            if (logger.isLevelEnabled(Level.TRACE) && argHolders.length > 0) {
                argValues = new Object[argHolders.length];
            }
            for (int i = 0; i < argHolders.length; i++) {
                Object argValue = argHolders[i].getSqlArgValue();
                FieldType fieldType = argFieldTypes[i];
                SqlType sqlType;
                if (fieldType == null) {
                    sqlType = argHolders[i].getSqlType();
                } else {
                    sqlType = fieldType.getSqlType();
                }
                stmt.setObject(i, argValue, sqlType);
                if (argValues != null) {
                    argValues[i] = argValue;
                }
            }
            logger.debug("prepared statement '{}' with {} args", statement, argHolders.length);
            if (argValues != null) {
                // need to do the (Object) cast to force args to be a single
                // object
                logger.trace("prepared statement arguments: {}", (Object) argValues);
            }
            ok = true;
            return stmt;
        } finally {
            if (!ok) {
                stmt.close();
            }
        }
    }
}
